Управление памятью
===================

.. index::
    управление памятью

D - это язык, который не требует явного управления памятью. Однако
системному программисту важно знать, как управлять памятью,
когда это необходимо для особых случаев.
Управление памятью - очень обширная тема. В этой главе мы рассмотрим только сборщик мусора (GC), выделением памяти из него и созданием объектов в определенных местах памяти. Я рекомендую вам изучить различные методы управления памятью
различные методы управления памятью, а также модуль std.allocator, который на момент написания этой главы все еще находился в
экспериментальной стадии на момент написания этой книги.
Как и в некоторых других предыдущих главах, когда я пишу "переменная", я имею в виду любой
тип переменной, включая объекты структур и классов

Память
------
Память является более значимым ресурсом, чем другие системные ресурсы, потому что и
выполняемая программа и ее данные находятся в памяти. Память
принадлежит в конечном итоге операционной системе, которая предоставляет ее программам
для удовлетворения их потребностей. Объем памяти, используемый программой, может увеличиваться
или уменьшаться в зависимости от непосредственных потребностей программы. Когда программа
завершается, используемые ею области памяти автоматически возвращаются
обратно в операционную систему.
Память можно представить себе как большой лист бумаги, на котором записаны значения
переменных. Каждая переменная хранится в определенном месте, где ее
значение записывается и считывается по мере необходимости. Как только время жизни переменной заканчивается,
ее место занимает другая переменная.
Оператор & (address-of) полезен при экспериментах с памятью. Например,
Например, следующая программа выводит адреса двух переменных, которые
которые определены рядом друг с другом:

.. code-block:: d

    import std.stdio;

    void main()
    {
        int i;
        int j;
        writeln("i: ", &i);
        writeln("j: ", &j);
    }

*Примечание: Адреса, скорее всего, будут отличаться при каждом выполнении программы.
Кроме того, сам факт получения адреса переменной отключает оптимизацию.
которая в противном случае могла бы заставить (прим. переводчика) переменную жить в регистре процессора.*
Как видно из вывода, адреса переменных находятся в четырех байтах
друг от друга:

.. code-block:: console

    i: 7FFF2B633E28
    j: 7FFF2B633E2C

Последние цифры двух адресов указывают на то, что i находится в ячейке памяти, которая
находится прямо перед местом расположения j: 8 плюс 4 (размер int) составляет 12 (C в шестнадцатеричной
нотации).

.. index::
    сборка мусора

Сборщик мусора
--------------

Динамические переменные, используемые в общих программах, хранятся в блоках памяти,
принадлежащих сборщику мусора (GC). Когда время жизни переменной заканчивается (т.е.
она больше не используется), эта переменная подлежит доработке в соответствии с
алгоритмом, который выполняется GC. Если
ячейка памяти, содержащая переменную, больше ничему не нужна, память может быть освобождена для размещения
других переменных. Этот алгоритм называется сборкой мусора, а выполнение
алгоритма называется циклом сборки мусора.
Алгоритм, который выполняет GC, можно приблизительно описать следующим образом.
Сканируются все блоки памяти, к которым можно получить прямой или косвенный доступ с помощью указателей
(включая ссылки), находящихся в корнях программы. Любой
блок памяти, к которому можно получить доступ, помечается как все еще используемый, а все остальные
помечаются как больше не используемые. Финализаторы объектов и структур, которые находятся
в недоступных блоках, выполняются, и эти блоки памяти освобождаются для
использования в будущих переменных.орни определяются как весь программный стек для
каждый поток, все глобальные и локальные переменные потока и любые дополнительные данные, добавленные
с помощью GC.addRoot или GC.AddRange.
Некоторые алгоритмы GC могут перемещать объекты, чтобы сохранить их вместе в одном
месте в памяти. Чтобы сохранить корректность программы, все указатели (и
ссылки), которые указывают на такие объекты, автоматически изменяются, чтобы указывать на
новые местоположения. Текущий GC D этого не делает.
GC называется "точным", если он точно знает, какая память содержит указатели
, а какая нет. GC является консервативным, если он сканирует всю память, как если бы она была
указатели. GC D частично консервативен, сканируя только блоки, содержащие
указатели, но он будет сканировать все данные в этих блоках. По этой причине в некоторых случаях
блоки никогда не собираются, что приводит к "утечке" этой памяти. Большие блоки
с большей вероятностью будут нацелены на "ложные указатели". В некоторых случаях может быть
рекомендовано вручную освободить большие блоки, которые вы больше не используете, чтобы избежать этой
проблемы.
Порядок выполнения финализаторов не указан. Например, ссылочный
элемент объекта может быть финализирован до того, как будет финализирован объект, содержащий этот элемент
член. По этой причине ни к одному члену класса, который ссылается на динамическую переменную
, не следует обращаться внутри деструктора. Обратите внимание, что это сильно отличается от
детерминированного порядка уничтожения в таких языках, как C++.
Цикл сборки мусора может быть запущен по разным причинам, например, для того, чтобы
найти место для большего количества данных. В зависимости от реализации GC, поскольку
выделение новых объектов во время цикла сборки мусора может повлиять на
сам процесс сбора, все запущенные потоки, возможно, придется остановить во время
циклы сбора. Иногда это можно наблюдать как колебания в выполнении
программы.
В большинстве случаев программисту не нужно вмешиваться в процесс
сбора мусора. Однако можно задерживать или отправлять
циклы сборки мусора по мере необходимости с помощью функций, определенных в модуле core.memory.


**Запуск и задержка циклов сборки мусора**

Может возникнуть желание отложить выполнение циклов сборки мусора в той части
программы, где важно, чтобы программа была отзывчивой.
GC.disable отключает циклы сборки мусора, а GC.enable включает их
снова:

.. code-block:: d

    GC.disable();
    // ... часть программы, где важна отзывчивость...
    GC.enable();

Однако GC.disable не гарантирует предотвращения цикла сборки мусора
от выполнения: Если GC необходимо получить больше памяти от ОС, но он
но не может, он все равно запустит цикл сборки мусора в качестве последней попытки
чтобы получить немного доступной памяти.
Вместо того чтобы полагаться на автоматическую сборку мусора в
в неустановленное время, цикл сборки мусора можно запустить явно, используя
GC.collect():

.. code-block:: d

    import core.memory;
    // ...
    GC.collect(); // starts a garbage collection cycle

Обычно GC не возвращает блоки памяти операционной системе; он
сохраняет эти страницы памяти для будущих нужд программы. При желании
GC можно попросить вернуть неиспользуемую память операционной системе с помощью команды
GC.minimize():

.. code-block:: d

    GC.minimize()

.. index::
    распределение памяти
    memory allocation

Распределение памяти
---------------------

Системные языки позволяют программистам указывать области памяти, в которых должны находиться объекты
должны жить. Такие области памяти принято называть буферами.
Существует несколько методов выделения памяти. Самый простой метод
это использование массива фиксированной длины:

.. code-block:: d

    ubyte[100] buffer; // A memory area of 100 bytes

Буфер готов к использованию в качестве 100-байтной области памяти. Вместо ubyte можно также
можно определить такие буфера как массивы void, без привязки к какому-либо
типу. Поскольку void нельзя присвоить какое-либо значение, он также не может иметь значение .init
тоже. Такие массивы должны быть инициализированы специальным синтаксисом =void:

.. code-block:: d

    void[100] buffer = void; // A memory area of 100 bytes

В этой главе мы будем использовать только GC.calloc из модуля core.memory для резервирования памяти.
этой главе. Этот модуль имеет множество других возможностей, полезных в различных
ситуациях. Кроме того, функции распределения памяти стандартной библиотеки языка Си
доступны в модуле core.stdc.stdlib.
GC.calloc выделяет область памяти указанного размера, предварительно заполненную всеми значениями 0
и возвращает начальный адрес выделенной области:

.. code-block:: d

    import core.memory;
    // ...
    void * buffer = GC.calloc(100);
    // A memory area of 100 zero bytes

Обычно возвращаемое значение void* приводится к указателю соответствующего типа:

.. code-block:: d

    int * intBuffer = cast(int*)buffer;

Однако этот промежуточный шаг обычно пропускается, и возвращаемое значение приводится
напрямую:

.. code-block:: d

    int * intBuffer = cast(int*)GC.calloc(100);

Вместо произвольных значений, таких как 100, размер области памяти обычно
рассчитывается путем умножения количества необходимых элементов на размер каждого элемента
элемента:

.. code-block:: d

    // Allocate room for 25 ints
    int * intBuffer = cast(int*)GC.calloc(int.sizeof * 25);

Для классов существует важное различие: Размер переменной класса и
размер объекта класса - это не одно и то же. .sizeof - это размер переменной класса, который
всегда одно и то же значение: 8 на 64-битных системах и 4 на 32-битных. Размер
объекта класса должен быть получен с помощью __traits(classInstanceSize):

.. code-block:: d

    // Allocate room for 10 MyClass objects
    MyClass* buffer =
        cast(MyClass*) GC.calloc(
            __traits(classInstanceSize, MyClass) * 10);

Если в системе не хватает памяти для запрашиваемого размера, то будет выброшено исключение
core.exception.OutOfMemoryError:

.. code-block:: d

    void * buffer = GC.calloc(10_000_000_000);

Вывод на системе, не имеющей столько свободного места:

.. code-block:: console

    core.exception.OutOfMemoryError

Области памяти, выделенные из GC, могут быть возвращены ему обратно с помощью функции
GC.free:

.. code-block:: d

    GC.free(buffer);

Однако вызов free() не обязательно приводит к выполнению деструкторов
переменных, находящихся в этом блоке памяти. Деструкторы могут быть выполнены
явно, вызвав destroy() для каждой переменной. Обратите внимание, что различные внутренние
механизмы используются для вызова финализаторов переменных классов и структур во время GC
сбора или освобождения. Лучший способ обеспечить их вызов - использовать оператор new
при выделении переменных. В этом случае GC.free вызовет деструкторы.
Иногда программа может определить, что ранее выделенная область памяти
использована и в ней нет места для новых данных. В этом случае можно расширить
ранее выделенную область памяти с помощью GC.realloc. realloc() берет
указатель ранее выделенной памяти и новый запрашиваемый размер, и возвращает
новую область:

.. code-block:: d

    void * oldBuffer = GC.calloc(100);
    // ...
    void * newBuffer = GC.realloc(oldBuffer, 200);

realloc() старается быть эффективной, фактически не выделяя новую память, если это не является
если это действительно необходимо:
* Если область памяти, следующая за старой областью, не используется для каких-либо других целей
и достаточно велика, чтобы удовлетворить новый запрос, realloc() добавляет эту часть
памяти к старой области, расширяя буфер на месте.
* Если область памяти, следующая за старой областью, уже используется или недостаточно велика
то realloc() выделяет новую область памяти большего размера и копирует
содержимое старой области в новую.


* Можно передать null в качестве oldBuffer, и в этом случае realloc() просто
  выделит новую память.
* Возможно передать размер меньше предыдущего, в этом случае
  оставшаяся часть старой памяти возвращается обратно в GC.
* Можно передать 0 в качестве нового размера, в этом случае realloc() просто освобождает
  память.

GC.realloc адаптирована из функции realloc() стандартной библиотеки C. Из-за такого сложного поведения считается, что realloc() считается, что у нее плохо продуманный интерфейс функции. Потенциально удивительным аспектом GC.realloc является то, что
даже если исходная память была выделена с помощью GC.calloc, расширенная
часть никогда не очищается. По этой причине, когда важно, чтобы память была
нулевой инициализации, будет полезна функция, подобная reallocCleared() ниже. Мы
рассмотрим значение blockAttributes ниже:

.. code-block:: d

    import core.memory;

    /* Works like GC.realloc but clears the extra bytes if memory
    * is extended. */
    void* reallocCleared(
        void* buffer,
        size_t oldLength,
        size_t newLength,
        GC.BlkAttr blockAttributes = GC.BlkAttr.NONE,
        const TypeInfo typeInfo = null)
    {
        /* Dispatch the actual work to GC.realloc. */
        buffer = GC.realloc(buffer, newLength,
            blockAttributes, typeInfo);
        /* Clear the extra bytes if extended. */
        if (newLength > oldLength)
        {
            import core.stdc.string;

            auto extendedPart = buffer + oldLength;
            const extendedLength = newLength - oldLength;
            memset(extendedPart, 0, extendedLength);
        }
        return buffer;
    }


Приведенная выше функция использует memset() из модуля core.stdc.string, чтобы очистить
новых расширенных байтов. memset() присваивает указанное значение байтам области памяти, заданной указателем и длиной.
области памяти, указанной указателем и длиной. В примере она присваивает 0
extendedLength количеству байтов в extendedPart.
Мы будем использовать reallocCleared() в примере ниже.
Поведение аналогичной функции GC.extend не такое сложное, как
realloc(); она применяет только первый пункт, описанный выше: Если область памяти не может быть
расширить на месте, extend() ничего не делает и возвращает 0.

.. index::
    атрибуты блока памяти

**Атрибуты блока памяти**

Концепции и шаги алгоритма GC могут быть в некоторой степени сконфигурированы
для каждого блока памяти с помощью перечисления BlkAttr. BlkAttr является необязательным параметром
GC.calloc и других функций выделения. Он состоит из следующих значений:
* NONE: Значение ноль; указывает на отсутствие атрибута.
* FINALIZE: Указывает, что объекты, находящиеся в блоке памяти, должны быть
финализированы.
Обычно GC предполагает, что время жизни объектов, которые живут в явно
выделенных областях памяти, находится под контролем программиста; он не
не финализирует объекты в таких областях памяти. GC.BlkAttr.FINALIZE предназначена для
запроса GC на выполнение деструкторов объектов:

.. code-block:: d

    Class* buffer =
    cast(Class*) GC.calloc(
        __traits(classInstanceSize, Class) * 10,
        GC.BlkAttr.FINALIZE);

Обратите внимание, что FINALIZE зависит от деталей реализации, правильно установленных в
блоке. Настоятельно рекомендуется позволить GC позаботиться о настройке этих
используя оператор new.
* NO_SCAN: Указывает, что область памяти не должна сканироваться GC.
Значения байтов в области памяти могут случайно выглядеть как указатели на
несвязанные объекты в других частях памяти. Когда это происходит, GC
предположит, что эти объекты все еще используются, даже после того как их реальное
даже после того, как их реальное время жизни закончилось.
Блок памяти, который, как известно, не содержит никаких указателей на объекты, должен быть
пометить как GC.BlkAttr.NO_SCAN:

.. code-block:: d

    int * intBuffer =  cast(int*)GC.calloc(100, GC.BlkAttr.NO_SCAN);

Переменные int, размещенные в этом блоке памяти, могут иметь любое значение без
опасения, что они будут приняты за указатели объектов.

* NO_MOVE: Указывает, что объекты в блоке памяти не должны перемещаться в
  другие места.
* APPENDABLE: Это внутренний флаг, используемый средой выполнения D для быстрого
  добавления. Вы не должны использовать этот флаг при выделении памяти.
* NO_INTERIOR: Указывает, что существуют только указатели на первый адрес блока.

Это позволяет сократить количество "ложных указателей", поскольку указатель на середину
блока не учитывается при отслеживании того, куда идет указатель.
Значения перечисления BlkAttr подходят для использования в качестве битовых флагов, которые мы рассматривали в главе
Глава :ref:`Битовые операции<глава_69_start>`. Ниже показано, как два атрибута могут быть
объединить с помощью оператора \|:

.. code-block:: d

    const attributes = GC.BlkAttr.NO_SCAN | GC.BlkAttr.NO_INTERIOR;

Естественно, GC будет знать только о тех блоках памяти, которые зарезервированы его
собственными функциями и сканирует только эти блоки памяти. Например, он не будет
например, он не будет знать о блоке памяти, выделенном core.stdc.stdlib.calloc.
GC.addRange предназначена для внесения в GC несвязанных блоков памяти.
Дополняющая функция GC.removeRange должна вызываться перед освобождением блока памяти
другими средствами, например, с помощью core.stdc.stdlib.free.
В некоторых случаях в программе может не быть ссылок на блок памяти
даже если этот блок памяти был зарезервирован GC. Например, если единственная
ссылка на блок памяти находится в библиотеке языка Си, GC обычно не
не будет знать об этой ссылке и предположит, что блок памяти больше не используется. GC.addRoot вводит блок памяти в GC в качестве корня, который будет сканироваться
во время циклов сбора. Все переменные, к которым можно обратиться напрямую или
косвенно через этот блок памяти, будут помечены как живые. Дополнение
функция GC.removeRoot должна вызываться, когда блок памяти больше не используется.

Пример расширения области памяти
Давайте разработаем простой шаблон struct, который работает как массив. Чтобы сделать
короткого примера, предоставим только функциональность добавления и доступа к
элементов.
Как и в случае с массивами, будем увеличивать емкость по мере необходимости. В следующей
программа использует функцию reallocCleared(), которая была определена выше:

.. code-block:: d

    struct Array(T)
    {
        T* buffer; // Memory area that holds the elements
        size_t capacity; // The element capacity of the buffer
        size_t length; // The number of actual elements
        /* Returns the specified element */
        T element(size_t index)
        {
            import std.string;

            enforce(index < length,
                format("Invalid index %s", index));
            return *(buffer + index);
        }
        /* Appends the element to the end */
        void append(T element)
        {
            writefln("Appending element %s", length);
            if (length == capacity)
            {
                /* There is no room for the new element; must
    * increase capacity. */
                size_t newCapacity = capacity + (capacity / 2) + 1;
                increaseCapacity(newCapacity);
            }
            /* Place the element at the end */
            *(buffer + length) = element;
            ++length;
        }

        void increaseCapacity(size_t newCapacity)
        {
            writefln("Increasing capacity from %s to %s",
                capacity, newCapacity);
            size_t oldBufferSize = capacity * T.sizeof;
            size_t newBufferSize = newCapacity * T.sizeof;
            /* Also specify that this memory block should not be
    * scanned for pointers. */
            buffer = cast(T*) reallocCleared(
                buffer, oldBufferSize, newBufferSize,
                GC.BlkAttr.NO_SCAN);
            capacity = newCapacity;
        }
    }

Емкость массива увеличивается примерно на 50 %. Например, после того как емкость для
100 элементов, новая емкость станет 151. (Дополнительная цифра 1 относится к
для случая длины 0, когда добавление 50 % не увеличит массив.)
Следующая программа использует этот шаблон с типом double:

.. code-block:: d

    import std.stdio;
    import core.memory;
    import std.exception;

    // ...
    void main()
    {
        auto array = Array!double();
        const count = 10;
        foreach (i; 0 .. count)
        {
            double elementValue = i * 1.1;
            array.append(elementValue);
        }
        writeln("The elements:");
        foreach (i; 0 .. count)
        {
            write(array.element(i), ' ');
        }
        writeln();
    }

Вывод:

.. code-block:: d

    Adding element with index 0
    Increasing capacity from 0 to 1
    Adding element with index 1
    Increasing capacity from 1 to 2
    Adding element with index 2
    Increasing capacity from 2 to 4
    Adding element with index 3
    Adding element with index 4
    Increasing capacity from 4 to 7
    Adding element with index 5
    Adding element with index 6
    Adding element with index 7
    Increasing capacity from 7 to 11
    Adding element with index 8
    Adding element with index 9
    The elements:
    0 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9

Выравнивание
------------

По умолчанию каждый объект размещается в ячейках памяти, кратных
количеству, характерному для типа этого объекта. Эта величина называется выравниванием
данного типа. Например, выравнивание int равно 4, потому что переменные int размещаются
в ячейках памяти, кратных 4 (4, 8, 12 и т. д.).
Выравнивание необходимо для обеспечения производительности процессора или требований к нему, поскольку доступ к
к адресам памяти с неправильным выравниванием может быть медленнее или привести к ошибке шины. Кроме того,
некоторые типы переменных могут корректно работать только по выровненным адресам

**Свойство .alignof**

Свойство .alignof типа является его значением выравнивания по умолчанию. Для классов,
.alignof - это выравнивание переменной класса, а не объекта класса. Выравнивание
выравнивание объекта класса можно получить с помощью
std.traits.classInstanceAlignment.
Следующая программа выводит выравнивания различных типов:

.. code-block:: d

    import std.stdio;
    import std.meta;
    import std.traits;

    struct EmptyStruct
    {
    }

    struct Struct
    {
        char c;
        double d;
    }

    class EmptyClass
    {
    }

    class Class
    {
        char c;
    }

    void main()
    {
        alias Types = AliasSeq!(char, short, int, long,
            double, real,
            string, int[int], int*,
            EmptyStruct, Struct,
            EmptyClass, Class);
        writeln(" Size  Alignment  Type\n",
            "=========================");
        foreach (Type; Types)
        {
            static if (is(Type == class))
            {
                size_t size = __traits(classInstanceSize, Type);
                size_t alignment = classInstanceAlignment!Type;
            }
            else
            {
                size_t size = Type.sizeof;
                size_t alignment = Type.alignof;
            }
            writefln("%4s%8s      %s",
                size, alignment, Type.stringof);
        }
    }

Вывод программы может отличаться в разных окружениях. На ниже приведен пример вывода:

.. code-block:: console

    Size  Alignment  Type
    =========================
    1       1      char
    2       2      short
    4       4      int
    8       8      long
    8       8      double
    16      16      real
    16       8      string
    8       8      int[int]
    8       8      int*
    1       1      EmptyStruct
    16       8      Struct
    16       8      EmptyClass
    17       8      Class

Ниже мы увидим, как переменные могут быть сконструированы (размещены) в определенных
местах памяти. Для корректности и эффективности объекты должны быть построены по
адресам, которые соответствуют их выравниванию.
Рассмотрим два последовательных объекта типа Class выше, которые имеют размер 17 байт
каждый. Хотя 0 не является законным адресом переменной на большинстве платформ, для
упрощения примера предположим, что первый объект находится по адресу 0. 17 байт
этого объекта будут находиться по адресам от 0 до 16:

.. image:: images/88.4_1.png

Хотя следующий доступный адрес - 17, это место не может быть использовано для объекта класса
потому что 17 не кратно значению выравнивания 8 для этого типа. По адресу
ближайший возможный адрес для второго объекта - 24, потому что 24 - следующий наименьший адрес.

Когда второй объект будет размещен по этому адресу, между двумя объектами останутся
неиспользованные байты между двумя объектами. Эти байты называются байтами заполнения (padding):

.. image:: images/88.4_2.png


Следующая формула позволяет определить ближайшее значение адреса, по которому можно разместить объект
может быть размещен:

.. code-block:: d

    (candidateAddress + alignmentValue - 1) / alignmentValue * alignmentValue

Чтобы эта формула работала, дробная часть результата деления должна быть
усеченной. Поскольку усечение происходит автоматически для целых типов, все переменные
выше предполагаются как интегральные типы.
В последующих примерах мы будем использовать следующую функцию:

.. code-block:: d

    T* nextAlignedAddress(T)(T* candidateAddr)
    {
        import std.traits;

        static if (is(T == class))
        {
            const alignment = classInstanceAlignment!T;
        }
        else
        {
            const alignment = T.alignof;
        }
        const result = (cast(size_t) candidateAddr + alignment - 1)
            / alignment * alignment;
        return cast(T*) result;
    }

Шаблон функции выводит тип объекта из своего параметра шаблона
параметра. Поскольку это невозможно, когда тип является void*, тип должен быть предоставлен
как явный аргумент шаблона для перегрузки void*. Эта перегрузка
может тривиально перенаправить вызов шаблона функции, приведенного выше:

.. code-block:: d

    void* nextAlignedAddress(T)(void* candidateAddr)
    {
        return nextAlignedAddress(cast(T*) candidateAddr);
    }

Приведенный выше шаблон функции будет полезен ниже при конструировании объектов класса
с помощью функции emplace().
Определим еще один шаблон функции для вычисления общего размера объекта
включая байты заполнения, которые должны быть помещены между двумя объектами этого типа:

.. code-block:: d

    size_t sizeWithPadding(T)()
    {
        static if (is(T == class))
        {
            const candidateAddr = __traits(classInstanceSize, T);
        }
        else
        {
            const candidateAddr = T.sizeof;
        }
        return cast(size_t) nextAlignedAddress(cast(T*) candidateAddr);
    }

.. index::
    .offsefof

**Свойство .offsetof**

Выравнивание соблюдается и для членов пользовательских типов. Между членами могут быть
байты прокладки между членами, чтобы члены были выровнены в соответствии с
соответствующим типам. По этой причине размер следующей структуры составляет не 6
байт, как можно было бы ожидать, а 12:

.. code-block:: d

    struct A
    {
        byte b; // 1 byte
        int i; // 4 bytes
        ubyte u; // 1 byte
    }

    static assert(A.sizeof == 12); // More than 1 + 4 + 1

Это связано с добавлением байтов перед членом int, чтобы он был выровнен по адресу
адресу, который кратен 4, а также байтов заполнения в конце для
выравнивания всего объекта struct.
Свойство .offsetof указывает количество байт, на которое переменная-член находится от
от начала объекта, частью которого она является. Следующая функция выводит
выводит расположение типа, определяя байты подстановки по .offsetof:

.. code-block:: d

    void printObjectLayout(T)() if (is(T == struct) || is(T == union))
    {
        import std.stdio;
        import std.string;

        writefln("=== Memory layout of '%s'" ~
                " (.sizeof: %s, .alignof: %s) ===",
                T.stringof, T.sizeof, T.alignof);
        /* Prints a single line of layout information. */
        void printLine(size_t offset, string info)
        {
            writefln("%4s: %s", offset, info);
        }
        /* Prints padding information if padding is actually
    * observed. */
        void maybePrintPaddingInfo(size_t expectedOffset,
            size_t actualOffset)
        {
            if (expectedOffset < actualOffset)
            {
                /* There is some padding because the actual offset
    * is beyond the expected one. */
                const paddingSize = actualOffset - expectedOffset;
                printLine(expectedOffset,
                    format("... %s-byte PADDING",
                        paddingSize));
            }
        }
        /* This is the expected offset of the next member if there
    * were no padding bytes before that member. */
        size_t noPaddingOffset = 0;
        /* Note: __traits(allMembers) is a 'string' collection of
    * names of the members of a type. */
        foreach (memberName; __traits(allMembers, T))
        {
            mixin(format("alias member = %s.%s;",
                    T.stringof, memberName));
            const offset = member.offsetof;
            maybePrintPaddingInfo(noPaddingOffset, offset);
            const typeName = typeof(member).stringof;
            printLine(offset,
                format("%s %s", typeName, memberName));
            noPaddingOffset = offset + member.sizeof;
        }
        maybePrintPaddingInfo(noPaddingOffset, T.sizeof);
    }

Следующая программа выводит макет 12-байтовой структуры A, которая была определена
выше:

.. code-block:: d

    struct A
    {
        byte b;
        int i;
        ubyte u;
    }

    void main()
    {
        printObjectLayout!A();
    }

Вывод программы показывает, где находятся в общей сложности 6 байтов заполнения
внутри объекта. Первый столбец вывода - это смещение от начала
объекта:

.. code-block:: console

    === Memory layout of 'A' (.sizeof: 12, .alignof: 4) ===
    0: byte b
    1: ... 3-byte PADDING
    4: int i
    8: ubyte u
    9: ... 3-byte PADDING

Одна из техник минимизации прокладок - упорядочивание членов по их размерам
от самого большого к самому маленькому. Например, если член int переместить в
в начало предыдущей структуры, то размер объекта будет меньше:

.. code-block:: d

    struct B
    {
        int i; // Moved up inside the struct definition
        byte b;
        ubyte u;
    }

    void main()
    {
        printObjectLayout!B();
    }

На этот раз размер объекта уменьшился до 8 за счет всего лишь 2 байт заполнения в конце
конце:

.. code-block:: console

    === Memory layout of 'B' (.sizeof: 8, .alignof: 4) ===
    0: int i
    4: byte b
    5: ubyte u
    6: ... 2-byte PADDING

.. index::
    align
    выравнивание

**Атрибут align**

Атрибут align предназначен для задания выравнивания переменных, определенных пользователем типов,
и членов пользовательских типов. Значение, указанное в круглых скобках, определяет
значение выравнивания. Каждое определение может быть указано отдельно. Например.
следующее определение выравнивает объекты S по 2-байтовым границам, а его член i
по 1-байтовым границам (1-байтовое выравнивание всегда приводит к отсутствию заполнения):

.. code-block:: d

    align(2) // The alignment of 'S' objects
    struct S
    {
        byte b;
        align(1) int i; // The alignment of member 'i'
        ubyte u;
    }

    void main()
    {
        printObjectLayout!S();
    }

Когда член int выровнен по 1-байтовой границе, перед ним нет никакого заполнителя.
и на этот раз размер объекта оказывается равным 6:

.. code-block:: console

    === Memory layout of 'S' (.sizeof: 6, .alignof: 4) ===
    0: byte b
    1: int i
    5: ubyte u

Хотя выравнивание может уменьшить размеры типов, определяемых пользователем, это может привести к существенной потери производительности,
когда не соблюдается стандартное выравнивание типов (а на
некоторых процессорах использование неправильно выровненных данных может привести к аварийному завершению программы).
align также может задавать выравнивание переменных:

.. code-block:: d

    align (32) double d;

Однако объекты, выделяемые по new, всегда должны быть выровнены по кратному
размеру типа size_t, потому что так предполагает GC. В противном случае это может быть причиной неопределенного поведения. Например, если size_t имеет длину 8 байт, то выравнивание
переменных, выделенных new, должно быть кратно 8.

Конструирование переменных в определенных местах памяти
-------------------------------------------------------
Выражение new решает три задачи:
#. Выделяет достаточно большой объем памяти для объекта. Вновь выделенная область памяти
считается необработанной, не связанной ни с типом, ни с объектом.
#. Копирует значение .init данного типа в эту область памяти и выполняет
конструктор объекта на этой области. Только после этого шага объект становится
размещенным в этой области памяти.
#. Конфигурирует блок памяти таким образом, чтобы он имел все необходимые флаги и
инфраструктуру для правильного уничтожения объекта при освобождении.
Мы уже видели, что первая из этих задач может быть явно выполнена с помощью
функции выделения памяти, такие как GC.calloc. Будучи системным языком, D позволяет
программисту управлять и вторым шагом.
Переменные можно создавать в определенных местах с помощью std.conv.emplace

**Создание объекта struct в определенном месте**

Функция emplace() принимает в качестве первого параметра адрес участка памяти и
создает объект в этом месте. Если он задан, то остальные
параметры в качестве аргументов конструктора объекта:

.. code-block:: d

    import std.conv;
    // ...
    emplace(address, /* ... constructor arguments ... */);

Нет необходимости указывать тип объекта явно при построении
struct object, поскольку emplace() выводит тип объекта из типа
указателя. Например, поскольку тип следующего указателя - Student*,
emplace() конструирует объект Student по этому адресу:

.. code-block:: d

    Student * objectAddr = nextAlignedAddress(candidateAddr);
    // ...
    emplace(objectAddr, name, id);

Следующая программа выделяет область памяти, достаточно большую для трех объектов
и конструирует их один за другим по выровненным адресам внутри этой области памяти:


.. code-block:: d

    import std.stdio;
    import std.string;
    import core.memory;
    import std.conv;

    // ...
    struct Student
    {
        string name;
        int id;
        string toString()
        {
            return format("%s(%s)", name, id);
        }
    }

    void main()
    {
        /* Some information about this type. */
        writefln("Student.sizeof: %#x (%s) bytes",
            Student.sizeof, Student.sizeof);
        writefln("Student.alignof: %#x (%s) bytes",
            Student.alignof, Student.alignof);
        string[] names = ["Amy", "Tim", "Joe"];
        const totalSize = sizeWithPadding!Student() * names.length;
        /* Reserve room for all Student objects.
    *
    * Warning! The objects that are accessible through this
    * slice are not constructed yet; they should not be
    * accessed until after they are properly constructed. */
        Student[] students =
            (cast(Student*) GC.calloc(totalSize))[0 .. names.length];
        foreach (i, name; names)
        {
            Student* candidateAddr = students.ptr + i;
            Student* objectAddr =
                nextAlignedAddress(candidateAddr);
            writefln("address of object %s: %s", i, objectAddr);
            const id = 100 + i.to!int;
            emplace(objectAddr, name, id);
        }
        /* All of the objects are constructed and can be used. */
        writeln(students);
    }

Вывод

.. code-block:: console

    Student.sizeof: 0x18 (24) bytes
    Student.alignof: 0x8 (8) bytes
    address of object 0: 7F1532861F00
    address of object 1: 7F1532861F18
    address of object 2: 7F1532861F30
    [Amy(100), Tim(101), Joe(102)]


**Конструирование объекта класса в определенном месте**

Переменные класса не обязательно должны быть того же типа, что и объекты класса. Например, переменная класса
переменная типа Animal может ссылаться на объект Cat. По этой причине функция emplace()
не определяет тип объекта по типу указателя памяти.
Вместо этого фактический тип объекта должен быть явно указан в качестве шаблонного
аргумента emplace(). (*Примечание:Кроме того, указатель класса - это указатель на переменную класса.
переменную, а не на объект класса. По этой причине указание фактического типа позволяет
программисту указать, что именно нужно поместить - объект класса или переменную класса.*)
Место в памяти для объекта класса должно быть указано в виде фрагмента void[] со следующим синтаксисом
следующим синтаксисом:


.. code-block:: d

    Type variable = emplace!Type(voidSlice,
    /* ... constructor arguments ... */);

emplace() создает объект класса в месте, указанном срезом, и
возвращает переменную класса для этого объекта.
Давайте применим функцию emplace() к объектам иерархии Animal. Объекты этой
иерархии будут размещены рядом друг с другом на участке памяти, выделенном с помощью
GC.calloc. Чтобы сделать пример более интересным, мы позаботимся о том, чтобы
подклассы имеют разные размеры. Это будет полезно для демонстрации того, как
адрес последующего объекта может быть определен в зависимости от размера
предыдущего.

.. code-block:: d

    interface Animal
    {
        string sing();
    }

    class Cat : Animal
    {
        string sing()
        {
            return "meow";
        }
    }

    class Parrot : Animal
    {
        string[] lyrics;
        this(string[] lyrics)
        {
            this.lyrics = lyrics;
        }

        string sing()
        {
            /* std.algorithm.joiner joins elements of a range with
    * the specified separator. */
            return lyrics.joiner(", ").to!string;
        }
    }

Буфер, в котором хранятся объекты, будет выделен с помощью GC.calloc:

.. code-block:: d

    const capacity = 10_000;
    void * buffer = GC.calloc(capacity);

Обычно необходимо убедиться, что для объектов всегда есть свободная память.
Здесь мы проигнорируем эту проверку для простоты примера и предположим, что
объекты в примере поместятся в десять тысяч байт.
Буфер будет использоваться для создания объектов Cat и Parrot:

.. code-block:: d

    Cat cat = emplace!Cat(catPlace);
    // ...
    Parrot parrot =
    emplace!Parrot(parrotPlace, [ "squawk", "arrgh" ]);

Обратите внимание, что аргумент конструктора Parrot указывается после адреса объекта
объекта.
Переменные, которые возвращает emplace(), будут храниться в фрагменте Animal, чтобы затем
чтобы впоследствии использовать в цикле foreach:

.. code-block:: d

    Animal[] animals;
    // ...
    animals ~= cat;
    // ...
    animals ~= parrot;
    foreach (animal; animals)
    {
        writeln(animal.sing());
    }

Дополнительные пояснение в коментариях к программе:

.. code-block:: d

    import std.stdio;
    import std.algorithm;
    import std.conv;
    import core.memory;

    // ...
    void main()
    {
        /* A slice of Animal variables (not Animal objects). */
        Animal[] animals;
        /* Allocating a buffer with an arbitrary capacity and
    * assuming that the two objects in this example will fit
    * in that area. Normally, this condition must be
    * validated. */
        const capacity = 10_000;
        void* buffer = GC.calloc(capacity);
        /* Let's first place a Cat object. */
        void* catCandidateAddr = buffer;
        void* catAddr = nextAlignedAddress!Cat(catCandidateAddr);
        writeln("Cat address   : ", catAddr);
        /* Since emplace() requires a void[] for a class object,
    * we must first produce a slice from the pointer. */
        size_t catSize = __traits(classInstanceSize, Cat);
        void[] catPlace = catAddr[0 .. catSize];
        /* Construct a Cat object inside that memory slice and
    * store the returned class variable for later use. */
        Cat cat = emplace!Cat(catPlace);
        animals ~= cat;
        /* Now construct a Parrot object at the next available
    * address that satisfies the alignment requirement. */
        void* parrotCandidateAddr = catAddr + catSize;
        void* parrotAddr =
            nextAlignedAddress!Parrot(parrotCandidateAddr);
        writeln("Parrot address: ", parrotAddr);
        size_t parrotSize = __traits(classInstanceSize, Parrot);
        void[] parrotPlace = parrotAddr[0 .. parrotSize];
        Parrot parrot =
            emplace!Parrot(parrotPlace, ["squawk", "arrgh"]);
        animals ~= parrot;
        /* Use the objects. */
        foreach (animal; animals)
        {
            writeln(animal.sing());
        }
    }

Вывод:

.. code-block:: d

    Cat address   : 7F0E343A2000
    Parrot address: 7F0E343A2018
    meow
    squawk, arrgh

Вместо того чтобы повторять шаги внутри main() для каждого объекта, шаблон функции
типа newObject(T) был бы более полезен.

Уничтожение объектов в явном виде
----------------------------------

Обратными операциями оператора new являются уничтожение объекта и возврат
память объекта обратно в GC. Обычно эти операции выполняются
автоматически в неопределенное время.
Однако иногда необходимо выполнить деструкторы в определенных точках
программе. Например, объект может закрывать член File в своем
деструктора, и деструктор должен быть выполнен немедленно, когда
время жизни объекта заканчивается.
destroy() вызывает деструктор объекта:

.. code-block:: d

    destroy(variable);

После выполнения деструктора destroy() устанавливает переменную в состояние .init.
Обратите внимание, что состояние .init переменной класса равно нулю; поэтому переменная класса не может быть использована
destroy() просто выполняет деструктор. Все еще решается
GC, когда повторно использовать часть памяти, которую раньше занимал
уничтоженный объект.
Предупреждение: При использовании указателя на структуру, destroy() должна получить  объект на который смотрит  указатель, а не сам указатель. В противном случае указатель будет установлен в null, а объект
объект не будет уничтожен:

.. code-block:: d

    import std.stdio;

    struct S
    {
        int i;
        this(int i)
        {
            this.i = i;
            writefln("Constructing object with value %s", i);
        }

        ~this()
        {
            writefln("Destroying object with value %s", i);
        }
    }

    void main()
    {
        auto p = new S(42);
        writeln("Before destroy()");
        destroy(p); // ← WRONG USAGE
        writeln("After destroy()");
        writefln("p: %s", p);
        writeln("Leaving main");
    }

Когда функция destroy() получает указатель, именно он уничтожается (т.е.
указатель становится равным null)

.. code-block:: console

    Constructing object with value 42
    Before destroy()
    After destroy() ← The object is not destroyed before this line
    p: null ← Instead, the pointer becomes null
    Leaving main
    Destroying object with value 42

По этой причине, когда используется указатель struct, функция destroy() должна получить не значение
указателя, а объект на который ссылается указатель:

.. code-block:: d

    destroy(*p); // ← Правильное использование

На этот раз деструктор выполняется в нужном месте, и указатель не устанавливается в
null:

.. code-block:: console

    Constructing object with value 42
    Before destroy()
    Destroying object with value 42 ← Destroyed at the right spot
    After destroy()
    p: 7FB64FE3F200 ← The pointer is not null
    Leaving main
    Destroying object with value 0 ← Once more for S.init

Последняя строка вызвана выполнением деструктора еще раз для одного и того же объекта,
который теперь имеет значение S.init.

Конструирование объектов во время выполнения по имени
------------------------------------------------------

Функция-член Object factory() принимает в качестве параметра полное имя
типа класса в качестве параметра, конструирует объект этого типа и возвращает переменную класса
переменную для этого объекта:

.. code-block:: d

    module test_module;
    import std.stdio;

    interface Animal
    {
        string sing();
    }

    class Cat : Animal
    {
        string sing()
        {
            return "meow";
        }
    }

    class Dog : Animal
    {
        string sing()
        {
            return "woof";
        }
    }

    void main()
    {
        string[] toConstruct = ["Cat", "Dog", "Cat"];
        Animal[] animals;
        foreach (typeName; toConstruct)
        {
            /* The pseudo variable __MODULE__ is always the name
    * of the current module, which can be used as a
    * string literal at compile time. */
            const fullName = __MODULE__ ~ '.' ~ typeName;
            writefln("Constructing %s", fullName);
            animals ~= cast(Animal) Object.factory(fullName);
        }
        foreach (animal; animals)
        {
            writeln(animal.sing());
        }
    }

Хотя в этой программе нет явного выражения new, три объекта класса
создаются и добавляются к фрагменту животных:

.. code-block:: d

    Constructing test_module.Cat
    Constructing test_module.Dog
    Constructing test_module.Cat
    meow
    woof
    meow

Обратите внимание, что Object.factory() принимает полное имя типа объекта
объекта. Кроме того, возвращаемый тип функции factory() - Object; поэтому перед использованием в программе его необходимо привести к
реальному типу объекта перед использованием в программе.

Резюме
-------

* Сборщик мусора сканирует память в неопределенное время, определяет объекты, к которым программа больше не может обратиться.
  объекты, к которым программа больше не может обратиться, уничтожаются память возвращается в GC
* Операции GC могут в некоторой степени контролироваться программистом
  GC.collect, GC.disable, GC.enable, GC.minimize и т. д.
* GC.calloc и другие функции резервируют память, GC.realloc расширяет
  расширяет ранее выделенную область памяти, а GC.free возвращает ее обратно в GC.
* Выделенную память можно пометить такими атрибутами, как
  GC.BlkAttr.NO_SCAN, GC.BlkAttr.NO_INTERIOR и т. д.
* Свойство .alignof - это выравнивание памяти типа по умолчанию. Выравнивание
  должно быть получено с помощью classInstanceAlignment для объектов класса.
* Свойство .offsetof - это количество байт, на которое член находится от
  начала объекта, частью которого он является.
* Атрибут align задает выравнивание переменной, определяемого пользователем типа,
  или члена.
* emplace() принимает указатель при построении объекта struct, фрагмент void[] при построении объекта class.
  при конструировании объекта класса.
* destroy() выполняет деструктор объектов. (Необходимо уничтожить указатель struct
  указатель, а не указатель на структуру).
* Object.factory() конструирует объекты по их полным именам типов.

